/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1/commercial
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may use this file in compliance with the License.
 * Alternatively, if you can't copy with the License, namely if you do not
 * want to provide source code of your modifications to this file, you may
 * negotiate a commercial licence with the initial developer.
 * You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is COID.
 *
 * The Initial Developer of the Original Code is
 * PosAm.
 * Portions created by the Initial Developer are Copyright (C) 2003
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 * Brano Kemen
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */


#ifndef __COID_CLIENT__HEADER_FILE__
#define __COID_CLIENT__HEADER_FILE__

#include "coid/comm/binstream/netstreamudp.h"
#include "coid/comm/binstream/netstreamcoid.h"
#include "coid/comm/binstream/netstreamhttp.h"
#include "coid/comm/binstream/exestream.h"
#include "coid/comm/local.h"
#include "coid/comm/ptrowner.h"
#include "coid/comm/dynarray.h"
#include "coid/comm/dir.h"
#include "coid/comm/version.h"
#include "coid/comm/rnd.h"
#include "coid/comm/sync/mutex_reg.h"


#include "coid.h"


#ifdef SYSTYPE_WIN
#include <process.h>
#else
#include <unistd.h>
#endif

#include <sys/stat.h>


namespace coid {



static ushort COID_NAMESERVER_PORT = 0;
static netAddress COID_NAMESERVER_ADDR;
const char COID_NAMESERVER_CONFIG[] = "cns.conf";

#define  COID_BROADCAST_SERVER_PORT  7999


#define extendedGUARD_lock(mx)      extendedGUARD_reg __coid__mxg( mx, false ); __coid__mxg.lock()




////////////////////////////////////////////////////////////////////////////////
struct ClientInstanceData
{
    void*  _cmdin;
    void*  _hptimer;
    uint   _flags;
    void*  _object;

    ClientInstanceData()
    {
        _cmdin = 0;
        _hptimer = 0;
        _flags = 0;
    }
};

////////////////////////////////////////////////////////////////////////////////
class COID_NICK
{
	charstr _ptr;
public:
	COID_NICK()
    {
#ifdef SYSTYPE_MSVC8plus
        size_t rs;
        getenv_s( &rs, 0, 0, "COID_NICK");
        if(rs>1)
            getenv_s( &rs, _ptr.get_append_buf(rs-1), rs, "COID_NICK" );
#else
        _ptr = getenv( "COID_NICK" );
#endif
    }

	friend binstream & operator << ( binstream & out, const COID_NICK & x ) { return out << x._ptr; }
};

////////////////////////////////////////////////////////////////////////////////
class extendedGUARD
{
	comm_mutex * _mx;
public:
	extendedGUARD( comm_mutex * mx, bool lock_now=true ) : _mx(mx)
	{
		if( lock_now ) lock();
	}
	~extendedGUARD() {
		if( _mx ) _mx->unlock();
	}
	void set( comm_mutex * mx ) {_mx = mx;}
	void eject() {_mx = NULL;}
	void lock() {
		if( _mx ) _mx->lock();
		else throw ersFE_EXCEPTION;
	}
};

////////////////////////////////////////////////////////////////////////////////
class extendedGUARD_reg
{
	comm_mutex_reg * _mx_reg;
	bool             _locked;

public:
	extendedGUARD_reg( comm_mutex_reg & mx_reg, bool lock_now=true ) : _mx_reg(&mx_reg), _locked(false)
	{
		if( lock_now ) lock();
	}
	~extendedGUARD_reg() {
		if( !_mx_reg  ) return;
		if( _locked && _mx_reg->is_set() ) _mx_reg->unlock();
	}
	//void set( comm_mutex_reg & mx_reg ) {_mx_reg = &mx_reg;}
	void eject() {_mx_reg = NULL; _locked = false;}
	void lock() {
		DASSERT( _mx_reg );
		if( _mx_reg->is_set() )
            try { _mx_reg->lock(); } catch( opcd ) { throw ersFE_DISCONNECTED; }
		else
            throw ersFE_DISCONNECTED;
		_locked = true;
	}
};





/////////////////////////////////////////////////////////////////////////////////////
struct REQUEST_PACKET
{
    uint arq_mag_beg;
    uint arq_extend;
    uchar arq_type;
    charstr arq_request;
    uint arq_mag_end;

    enum {
        MAGIC_BEG = 0xd0f0baca,
        MAGIC_END = 0x1e03bac1,

        IM_LIVE   = 0xABCDEF12,

        TYPE_IS_LIVE  = 0,
        TYPE_GET_ADDR = 1,
        TYPE_PUT_ADDR = 2,
    };

    REQUEST_PACKET() : arq_mag_beg(0), arq_extend(0), arq_type(0), arq_mag_end(0) { }

    bool is_valid() const { return ((arq_mag_beg == MAGIC_BEG) && (arq_mag_end == MAGIC_END)); }

    uint  pack_type() const { return arq_type; }

    void reset() {
        arq_extend = arq_type = 0;
        arq_request.reset();
    }

    bool is_live(uint _rnd) { return (arq_extend == _rnd); }

    void baddr_list_rq(const token& rq_str)
    {
        rnd_int rnd( (uint)time(0) );

        arq_mag_beg = REQUEST_PACKET::MAGIC_BEG;
        arq_mag_end = REQUEST_PACKET::MAGIC_END;
        arq_type = REQUEST_PACKET::TYPE_GET_ADDR;
        arq_extend = rnd.rand();
        arq_request = rq_str;
    }

    void baddr_update_rq(const token& rq_str)
    {
        rnd_int rnd( (uint)time(0) );

        arq_mag_beg = REQUEST_PACKET::MAGIC_BEG;
        arq_mag_end = REQUEST_PACKET::MAGIC_END;
        arq_type = REQUEST_PACKET::TYPE_PUT_ADDR;
        arq_extend = rnd.rand();
        arq_request = rq_str;
    }

    void live_test_rq()
    {
        rnd_int rnd( (uint)time(0) );

        arq_mag_beg = MAGIC_BEG;
        arq_mag_end = MAGIC_END;
        arq_type = TYPE_IS_LIVE;
        arq_extend = rnd.rand();
        arq_request = "";
    }

    friend binstream& operator << (binstream& out, const REQUEST_PACKET& t)
    {
        out << t.arq_mag_beg << t.arq_extend << t.arq_type << t.arq_request << t.arq_mag_end;
        return out;
    }

    friend binstream& operator >> (binstream& in, REQUEST_PACKET& t)
    {
        in >> t.arq_mag_beg >> t.arq_extend >> t.arq_type >> t.arq_request >> t.arq_mag_end;
        return in;
    }

};



/////////////////////////////////////////////////////////////////////////////////////
struct ADDRESS_RESPONSE
{
    uint arp_mag_beg;
    uint arp_rand;
    uchar arp_type;
    dynarray<netaddr> arp_response;
    uint arp_mag_end;

    enum {
        MAGIC = 0xFEDACABE,
        MAGIC_UPD = 0xADD0ADD0,

        TYPE_GET_ADDR = 1,
        TYPE_PUT_ADDR
    };

    ADDRESS_RESPONSE() : arp_mag_beg(0), arp_rand(0), arp_type(0), arp_mag_end(0) { }

    void reset() {
        arp_mag_beg = arp_mag_end = arp_type = 0;
        arp_rand = 0;
        arp_response.reset();
    }

    void set_get_addr(uint _rnd=0)
    {
        arp_mag_beg = arp_mag_end = MAGIC;
        arp_rand = _rnd;
        arp_type = TYPE_GET_ADDR;
    }

    void set_put_addr(uint _rnd=0)
    {
        arp_mag_beg = arp_mag_end = MAGIC;
        arp_rand = _rnd;
        arp_type = TYPE_PUT_ADDR;
    }

    bool is_valid(uint _rnd) const { return (arp_rand == _rnd) && (arp_mag_beg == MAGIC) && (arp_mag_end == MAGIC); }

    friend binstream& operator << (binstream& out, const ADDRESS_RESPONSE& t)
    {
        out << t.arp_mag_beg << t.arp_rand << t.arp_type << t.arp_response << t.arp_mag_end;
        return out;
    }

    friend binstream& operator >> (binstream& in, ADDRESS_RESPONSE& t)
    {
        in >> t.arp_mag_beg >> t.arp_rand >> t.arp_type >> t.arp_response >> t.arp_mag_end;
        return in;
    }
};


////////////////////////////////////////////////////////////////////////////////
class COID_CLIENT
{
//	uint				_coid_flags;

public:
	netstream *         _coid_bstream;

	uint				_coid_inst_id;
	mutable opcd		_coid_err_code;		/// use only if return value of method is not opcd

/*	enum {
		BSTREAM_OWNER                   = 1,
		WAIT_FOR_STARTING_SERVICES      = 2,
	};*/


public:
	COID_CLIENT( netstream * b=NULL, binstream * b2=NULL ) {
//		netSubsystem::instance();
		_coid_bstream = b;
		if( b2 ) *b2 >> _coid_inst_id;
		else if( _coid_bstream ) {
			*_coid_bstream >> _coid_inst_id;
//			_coid_flags = WAIT_FOR_STARTING_SERVICES | BSTREAM_OWNER;
		}
		else {
			_coid_inst_id = UMAX32;
//			_coid_flags = WAIT_FOR_STARTING_SERVICES;
		}
	}

/*	COID_CLIENT::~COID_CLIENT()
	{
		try {
			disconnect();
		} catch(...) {}
	}
*/


public:

	netstream * get_stream() {return _coid_bstream;}



//	void destroy_stream() {_coid_bstream = NULL;}	/// comm_mutex_reg will destroy it

	bool is_stream_open() const {return _coid_bstream ? _coid_bstream->is_open() : false;}

	void set_last_error( opcd e ) {_coid_err_code = e;}
	opcd get_last_error() const {return _coid_err_code;}



/*
	void sleep( long sec, long nsec=0 ) {
		timespec t = {sec, nsec};
		pthread_delay_np( &t );
	}
*/


//	bool is_connected () const { return _coid_inst_id != UMAX32  &&  _coid_bstream.is_set(); }


	opcd disconnect()
	{
		//if (!_coid_bstream.is_set()) return 0;
		if ( ! _coid_bstream ) return 0;
		if (_coid_inst_id != UMAX32) {
			opcd rc;
			try {
				(*_coid_bstream) << (uint) StdProtocolMethod::DISCONNECT << _coid_inst_id;
				_coid_bstream->flush();
				(*_coid_bstream) >> rc;
				_coid_bstream->acknowledge();
			}
            catch ( thread::Exception & ) { throw; }
			catch (opcd e) {
				rc = e;
			}
			_coid_inst_id = UMAX32;
			//destroy_stream();

			return rc;
		}
		return 0;
	}

    static netstream* create_stream( const char * service, const char * addr, charstr& dest_addr, bool tunneling = false )
    {
	    if(!addr)
            addr = "localhost:55099";
        else if( addr[0] == '=' )
        {
            tunneling = true;
            ++addr;
        }

        local<netstream> coid_bstream;
        if( tunneling )
            coid_bstream = new netstreamhttp;
        else
            coid_bstream = new netstreamcoid;

		opcd e = 0;
        if( addr && addr[0] == 0 ) {
			DASSERT( service );

			dynarray<netAddress> lst;
			if( !request_server(service, lst) ) return NULL;
			if( !lst.size() ) return NULL;

			charstr tmp_addr;
            for( uint i=0; i<lst.size(); ++i ) {
				tmp_addr.reset();
				lst[i].getHost( tmp_addr, true );
				e = coid_bstream->connect( tmp_addr, 55099, false );
				if(!e)
					break;
            }
        }
		else {
			e = coid_bstream->connect( addr, tunneling ? 80 : 55099, false );
		}

		if(e) return NULL;

		netAddress a;
		coid_bstream->get_remote_address( &a );
		a.getHost( dest_addr, true );

		return coid_bstream.eject();
    }


    static bool request_server( const token& name, dynarray<netAddress> & servers )
    {
        REQUEST_PACKET rp;
        netSubsystem::instance();

        rp.baddr_list_rq( name );

        if( COID_NAMESERVER_PORT == 0 )
        {
            if( directory::is_valid_file(COID_NAMESERVER_CONFIG) )
            {
                bifstream bif(COID_NAMESERVER_CONFIG);
                if( !bif.is_open() )  return false;

                char buf[64];
                uints bsz = 64;
                bif.read_raw( buf, bsz );

                token t( buf, 64-bsz );
                t.skip_ingroup(" \t\n\r");

                token port = t;
                port.cut_left(':');
                //token port = t.select_right(':',true);
                if( !port.is_empty() )
                {
                    COID_NAMESERVER_ADDR.set( t, COID_BROADCAST_SERVER_PORT, false );
                    COID_NAMESERVER_PORT = COID_NAMESERVER_ADDR.getPort();
                }
                else
                    COID_NAMESERVER_PORT = port.touint();
            }
            else
                COID_NAMESERVER_PORT = COID_BROADCAST_SERVER_PORT;
        }

		netstreamudp udp;
		if( COID_NAMESERVER_PORT == 1 )
			udp.set_remote_address( COID_NAMESERVER_ADDR );
		else
			udp.set_broadcast_address( COID_NAMESERVER_PORT );

		for( int retr=0; retr<4; retr++ ) {
			udp << rp << BINSTREAM_FLUSH;

			// wait 500 msec for the first response
			uint to = 500;
			for(;;)
			{
				if (!udp.data_available( to ))  break;

				to = 50;

				ADDRESS_RESPONSE ar;
				udp >> ar >> BINSTREAM_ACK;

				if (!ar.is_valid( rp.arq_extend ))
					continue;

				uints n = ar.arp_response.size();
				servers.need (n);
				for (uint i=0; i<n; ++i)
					netaddr2NetAddress (&servers[i], &ar.arp_response[i]);

				return true;
			}
		}

        return false;
    }
};



} // namespace coid



using namespace coid;



#endif	// ! __COID_CLIENT__HEADER_FILE__
